透過 Koa 來寫 Web 框架,很輕鬆就可以寫出一個 RESTful API。這篇會示範如何以 koa-router,寫一個簡單具有 CRUD 功能的 RESTful API。
在開始之前,我會建議你先閱讀 API 實作(一):規劃 RESTful API 要注意什麼,以了解什麼是 HTTP Method、HTTP Status,甚至是一些命名原則。
什麼是 CRUD?
CRUD 是 Create、Read、Update、Delete,對應到中文的新增、查詢、修改、刪除。
這篇我們就來寫簡單的文章系統好了。大概想一下會有四支東西:新增文章、編輯文章、查看文章、刪除文章。對應到的 HTTP Method 分別是:POST
、PUT
、GET
、DELETE
。
再來決定一個文章會有多少欄位:大概想一想應該會有標題、內容、時間、作者,就先簡單四個就好。
所以新增文章應該除了時間以外三個欄位都要送,沒送的話就噴錯(400 錯誤)、編輯亦同,多送一個文章 ID。讀取的時候只需要送文章 ID 就好,刪除的時候也只要送一個文章 ID 就好。
大概整理下來 API 文件可能會長這樣:
## 新增文章
`POST` `/article`
### 傳入資料
- title: (必填)標題
- body: (必填)內容
- author:(必填)作者
### 回傳狀態
- 201:已新增
- 400:有欄位未填
### 回傳資料
- id:新增的文章 id
## 編輯文章
`PUT` `/article/:id`
### 傳入資料
- id:(必填)文章 ID
- title:(必填)標題
- body:(必填)內容
### 回傳狀態
- 204:已編輯
- 400:有欄位未填
- 404:文章不存在
## 查看文章
`GET` `/article/:id`
### 傳入資料
- id:文章 ID
### 回傳資料
- title
- body
- author
- time
### 回傳狀態
- 200:附上文章內容
- 404:文章不存在
## 刪除文章
`DELETE` `/article/:id`
### 傳入資料
- id:文章 id
### 回傳狀態
- 204:已刪除
- 404:文章不存在
API 文件其實可以用 jsDoc、apiDoc 等規範,但我個人推薦以 Markdown 格式撰寫。
依照上一篇 Koa 文章以後,我們已經大概了解如何宣告 Koa、如何以 koa-router
去設定不同方法不同網址的路由、以及什麼是 Middleware,但我們還不知道怎麼讀取表單送過來的資料。這邊我們需要借用 koa-body
這個 Middleware,所以首先安裝:
npm install --save koa-body
接著應該可以弄出這樣的架構:
const Koa = require('koa');
const Router = require('koa-router');
const koaBody = require('koa-body');
const app = new Koa();
const router = new Router();
app.use(koaBody());
router
.post('/article', ctx => { /* ... */ })
.put('/article/:id', ctx => { /* ... */ })
.get('/article/:id', ctx => { /* ... */ })
.delete('/article/:id', ctx => { /* ... */ });
app.use(router.routes());
app.listen(3000);
中間四個分別填上 CURD 的邏輯,可能是資料庫系統:如 MongoDB、MySQL 等等。後面的文章會討論怎麼架設資料庫,但這邊我們先以一個整數來計算我們新文章的 ID 要塞什麼,並以 JSON Object 來儲存我們的文章(換句話說這支 JS 關掉資料就不在了),所以我們在 router 的四個 function 前先宣告一個變數 articles
陣列:
let lastId = 0;
let articles = [];
首先是新增文章的部分,我們需要讀取表單送過來的資料,表單送過來的資料會放在 ctx.request.body
裡面,把這些資料塞到 articles
裡面:
ctx => {
// 把資料分別存在 title、body、author 等變數
const { title } = ctx.request.body;
const { body } = ctx.request.body;
const { author } = ctx.request.body;
if (title && body && author) {
// 如果必填資料都有,就塞進 articles 裡面。然後依照文件回傳 201
articles.push({
id: ++lastId,
title,
body,
author,
time: new Date(),
});
ctx.status = 201;
ctx.body = lastId;
} else {
// 如果有欄位沒有填,就依照文件回傳 400
ctx.status = 400;
}
}
編輯的部分大同小異,差別是網址的那個 :id
要從 ctx.parmas
拿:
ctx => {
// 把資料分別存在 id、title、body、author 等變數
const id = parseInt(ctx.params.id);
const { title } = ctx.request.body;
const { body } = ctx.request.body;
const { author } = ctx.request.body;
if (title && body && author) {
// 如果必填資料都有,就編輯文章
// 首先找出文章
const article = articles.find(x => x.id === id);
if (article) {
// 如果有文章的話就編輯,並依照文件回傳 204
article.title = title;
article.body = body;
article.author = author;
article.time = new Date();
ctx.status = 204;
} else {
// 沒有找到的話就依照文件回傳 404
ctx.status = 404;
}
} else {
// 如果有欄位沒有填,就依照文件回傳 400
ctx.status = 400;
}
}
查看的部分也是找出那篇文章,然後回傳:
ctx => {
// 把資料分別存在 id 變數
const id = parseInt(ctx.params.id);
if (id) {
// 首先找出文章
const article = articles.find(x => x.id === id);
if (article) {
// 如果有文章的話就依照文件回傳文章內容(預設就是狀態 200)
ctx.body = article;
} else {
// 沒有找到的話就依照文件回傳 404
ctx.status = 404;
}
} else {
// 如果沒送 id,文章就不存在,就依照文件回傳 404
ctx.status = 404;
}
}
刪除的部分也是這樣做:
ctx => {
// 把資料分別存在 id 變數
const id = parseInt(ctx.params.id);
if (id) {
// 首先找出文章
const article = articles.find(x => x.id === id);
if (article) {
// 如果有文章的話就刪除文章,然後依照文件回傳 204
articles = articles.filter(x => x.id !== id);
ctx.status = 204;
} else {
// 沒有找到的話就依照文件回傳 404
ctx.status = 404;
}
} else {
// 如果沒送 id,文章就不存在,就依照文件回傳 404
ctx.status = 404;
}
}
這樣就寫出基本的 Koa API 了,如果想看完整程式碼,可以看看 這個完整範例。下一篇文章會介紹怎麼用工具測試 API。
本篇文章同步發表在 Noob's Space。
新版本的koa body好像得這樣才能使用
const { koaBody } = require('koa-body');